/*
Copyright 2008-2010 Gephi
Authors : Mathieu Bastian <mathieu.bastian@gephi.org>
Website : http://www.gephi.org

This file is part of Gephi.

Gephi is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

Gephi is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with Gephi.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.gephi.visualization.opengl;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import org.gephi.visualization.apiimpl.ModelImpl;

import javax.media.opengl.GL;
import javax.media.opengl.glu.GLU;
import org.gephi.graph.api.Model;
import org.gephi.graph.api.Renderable;
import org.gephi.visualization.VizArchitecture;
import org.gephi.visualization.VizController;
import org.gephi.visualization.apiimpl.Engine;
import org.gephi.visualization.apiimpl.GraphIO;
import org.gephi.visualization.apiimpl.VizEventManager;
import org.gephi.visualization.api.objects.ModelClass;
import org.gephi.visualization.api.objects.ModelClassLibrary;
import org.gephi.visualization.apiimpl.Scheduler;
import org.gephi.visualization.apiimpl.VizConfig;
import org.gephi.visualization.api.selection.SelectionArea;
import org.gephi.visualization.bridge.DataBridge;
import org.gephi.visualization.bridge.EventBridge;
import org.gephi.lib.gleem.linalg.Vecf;
import org.gephi.visualization.mode.ModeManager;
import org.gephi.visualization.opengl.octree.Octree;
import org.gephi.visualization.opengl.text.TextManager;
import org.gephi.visualization.swing.GraphDrawableImpl;

/**
 * Abstract graphic engine. Real graphic engines inherit from this class and can use the common functionalities.
 *
 * @author Mathieu Bastian
 */
public abstract class AbstractEngine implements Engine, VizArchitecture {

    //Enums
    public enum Limits {

        MIN_X, MAX_X, MIN_Y, MAX_Y, MIN_Z, MAX_Z
    };
    public static final int CLASS_NODE = 0;
    public static final int CLASS_EDGE = 1;
    public static final int CLASS_ARROW = 2;
    public static final int CLASS_POTATO = 3;
    //Architecture
    protected GraphDrawableImpl graphDrawable;
    protected GraphIO graphIO;
    protected VizEventManager vizEventManager;
    protected SelectionArea currentSelectionArea;
    protected ModelClassLibrary modelClassLibrary;
    protected DataBridge dataBridge;
    protected EventBridge eventBridge;
    protected VizController vizController;
    protected VizConfig vizConfig;
    protected ModeManager modeManager;
    protected TextManager textManager;
    //States
    protected boolean rectangleSelection;
    protected boolean customSelection;
    protected EngineLifeCycle lifeCycle = new EngineLifeCycle();
    protected boolean configChanged = false;
    protected boolean backgroundChanged = false;
    protected boolean reinit = false;
    protected float lightenAnimationDelta = 0f;
    //Octree
    protected Octree octree;

    public void initArchitecture() {
        this.graphDrawable = VizController.getInstance().getDrawable();
        this.graphIO = VizController.getInstance().getGraphIO();
        this.modelClassLibrary = VizController.getInstance().getModelClassLibrary();
        this.dataBridge = VizController.getInstance().getDataBridge();
        this.eventBridge = VizController.getInstance().getEventBridge();
        this.vizController = VizController.getInstance();
        this.vizConfig = VizController.getInstance().getVizConfig();
        this.modeManager = VizController.getInstance().getModeManager();
        this.textManager = VizController.getInstance().getTextManager();
        initObject3dClass();
        initSelection();

        //Vizconfig events
        vizController.getVizModel().addPropertyChangeListener(new PropertyChangeListener() {

            public void propertyChange(PropertyChangeEvent evt) {
                configChanged = true;
                if (evt.getPropertyName().equals("backgroundColor")) {
                    backgroundChanged = true;
                } else if (evt.getPropertyName().equals("use3d")) {
                    reinit = true;
                }

                getModelClasses()[AbstractEngine.CLASS_EDGE].setEnabled(vizController.getVizModel().isShowEdges());
                getModelClasses()[AbstractEngine.CLASS_ARROW].setEnabled(vizController.getVizModel().isShowEdges() && vizConfig.isShowArrows());
                getModelClasses()[AbstractEngine.CLASS_POTATO].setEnabled(vizController.getVizModel().isShowHulls());
            }
        });
    }

    public abstract void beforeDisplay(GL gl, GLU glu);

    public abstract void display(GL gl, GLU glu);

    public abstract void afterDisplay(GL gl, GLU glu);

    public abstract void initEngine(GL gl, GLU glu);

    public abstract void initScreenshot(GL gl, GLU glu);

    public abstract void cameraHasBeenMoved(GL gl, GLU glu);

    public abstract void mouseMove();

    public abstract void mouseDrag();

    public abstract void startDrag();

    public abstract void stopDrag();

    public abstract void mouseClick();

    public abstract Scheduler getScheduler();

    public abstract void addObject(int classID, ModelImpl obj);

    public abstract void removeObject(int classID, ModelImpl obj);

    public abstract void worldUpdated(int cacheMarker);

    public abstract void updateObjectsPosition();

    public abstract boolean updateWorld();

    public abstract void refreshGraphLimits();

    public abstract void initObject3dClass();

    public abstract void initSelection();

    public abstract ModelClass[] getModelClasses();

    protected abstract void startAnimating();

    protected abstract void stopAnimating();

    public abstract ModelImpl[] getSelectedObjects(int modelClass);

    public abstract void selectObject(Model obj);

    public abstract void selectObject(Model[] objs);

    public abstract void resetSelection();

    /**
     * Reset contents of octree for the given class
     */
    public abstract void resetObjectClass(ModelClass object3dClass);

    public float cameraDistance(ModelImpl object) {
        float[] cameraLocation = graphDrawable.getCameraLocation();
        double distance = Math.sqrt(Math.pow((double) object.getObj().x() - cameraLocation[0], 2d)
                + Math.pow((double) object.getObj().y() - cameraLocation[1], 2d)
                + Math.pow((double) object.getObj().z() - cameraLocation[2], 2d));
        object.setCameraDistance((float) distance);

        return (float) distance - object.getObj().getRadius();
    }

    protected void setViewportPosition(ModelImpl object) {
        double[] res = graphDrawable.myGluProject(object.getObj().x(), object.getObj().y(), object.getObj().z());
        object.setViewportX((float) res[0]);
        object.setViewportY((float) res[1]);

        res = graphDrawable.myGluProject(object.getObj().x() + object.getObj().getRadius(), object.getObj().y(), object.getObj().z());
        float rad = Math.abs((float) res[0] - object.getViewportX());
        object.setViewportRadius(rad);
    }

    public void reinit() {
        reinit = true;
    }

    protected boolean isUnderMouse(ModelImpl obj) {
        if (vizConfig.isEnableAutoSelect() && obj.isAutoSelected()) {
            return true;
        }
        if (obj.onlyAutoSelect()) {
            return false;
        }
        if (!currentSelectionArea.isEnabled()) {
            return false;
        }
        float x1 = graphIO.getMousePosition()[0];
        float y1 = graphIO.getMousePosition()[1];

        float x2 = obj.getViewportX();
        float y2 = obj.getViewportY();

        float xDist = Math.abs(x2 - x1);
        float yDist = Math.abs(y2 - y1);

        float distance = (float) Math.sqrt(xDist * xDist + yDist * yDist);

        Vecf d = new Vecf(5);
        d.set(0, xDist);
        d.set(1, yDist);
        d.set(2, distance);

        return currentSelectionArea.mouseTest(d, obj);
    }

    public SelectionArea getCurrentSelectionArea() {
        return currentSelectionArea;
    }

    public boolean isRectangleSelection() {
        return rectangleSelection;
    }

    public void setRectangleSelection(boolean rectangleSelection) {
        vizConfig.setRectangleSelection(rectangleSelection);
        configChanged = true;
        lightenAnimationDelta = 0;
        vizConfig.setLightenNonSelected(false);
    }

    public void setConfigChanged(boolean configChanged) {
        this.configChanged = configChanged;
    }

    public void startDisplay() {
        lifeCycle.requestStartAnimating();
    }

    public void stopDisplay() {
        lifeCycle.requestStopAnimating();
    }

    public Octree getOctree() {
        return octree;
    }

    protected class EngineLifeCycle {

        private boolean inited;
        private boolean requestAnimation;

        public void requestStartAnimating() {
            if (inited) {
                startAnimating();
            } else {
                requestAnimation = true;
            }
        }

        public void requestStopAnimating() {
            if (inited) {
                stopAnimating();
            }
        }

        public void initEngine() {
        }

        public boolean isInited() {
            return inited;
        }

        public void setInited() {
            if (!inited) {
                inited = true;
                if (requestAnimation) {
                    //graphDrawable.display();
                    startAnimating();
                    requestAnimation = false;
                }
            } else {
                dataBridge.reset();
                textManager.initArchitecture();
            }
        }
    }
}
